{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==((====))==  Unsloth 2024.12.2: Fast Llama patching. Transformers:4.46.3.\n",
      "   \\\\   /|    GPU: NVIDIA GeForce RTX 4090. Max memory: 23.513 GB. Platform: Linux.\n",
      "O^O/ \\_/ \\    Torch: 2.5.1. CUDA: 8.9. CUDA Toolkit: 12.1. Triton: 3.1.0\n",
      "\\        /    Bfloat16 = TRUE. FA [Xformers = 0.0.28.post3. FA2 = False]\n",
      " \"-____-\"     Free Apache license: http://github.com/unslothai/unsloth\n",
      "Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!\n"
     ]
    }
   ],
   "source": [
    "from unsloth import FastLanguageModel\n",
    "import torch\n",
    "max_seq_length = 2048 # Choose any! We auto support RoPE Scaling internally!\n",
    "dtype = None # None for auto detection. Float16 for Tesla T4, V100, Bfloat16 for Ampere+\n",
    "load_in_4bit = True # Use 4bit quantization to reduce memory usage. Can be False.\n",
    "MODEL_SIZE = \"3B\" # Can be \"0.5B\", \"1.5B\", \"3B\", \"14B\", \"32B\", \"72B\"\n",
    "\n",
    "\n",
    "# 4bit pre quantized models we support for 4x faster downloading + no OOMs.\n",
    "fourbit_models = [\n",
    "    \"unsloth/Meta-Llama-3.1-8B-bnb-4bit\",      # Llama-3.1 15 trillion tokens model 2x faster!\n",
    "    \"unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit\",\n",
    "    \"unsloth/Meta-Llama-3.1-70B-bnb-4bit\",\n",
    "    \"unsloth/Meta-Llama-3.1-405B-bnb-4bit\",    # We also uploaded 4bit for 405b!\n",
    "    \"unsloth/Mistral-Nemo-Base-2407-bnb-4bit\", # New Mistral 12b 2x faster!\n",
    "    \"unsloth/Mistral-Nemo-Instruct-2407-bnb-4bit\",\n",
    "    \"unsloth/mistral-7b-v0.3-bnb-4bit\",        # Mistral v3 2x faster!\n",
    "    \"unsloth/mistral-7b-instruct-v0.3-bnb-4bit\",\n",
    "    \"unsloth/Phi-3.5-mini-instruct\",           # Phi-3.5 2x faster!\n",
    "    \"unsloth/Phi-3-medium-4k-instruct\",\n",
    "    \"unsloth/gemma-2-9b-bnb-4bit\",\n",
    "    \"unsloth/gemma-2-27b-bnb-4bit\",            # Gemma 2x faster!\n",
    "] # More models at https://huggingface.co/unsloth\n",
    "\n",
    "model, tokenizer = FastLanguageModel.from_pretrained(\n",
    "    # Can select any from the below:\n",
    "    # \"unsloth/Qwen2.5-0.5B\", \"unsloth/Qwen2.5-1.5B\", \"unsloth/Qwen2.5-3B\"\n",
    "    # \"unsloth/Qwen2.5-14B\",  \"unsloth/Qwen2.5-32B\",  \"unsloth/Qwen2.5-72B\",\n",
    "    # And also all Instruct versions and Math. Coding verisons!\n",
    "    model_name = f\"unsloth/Llama-3.2-{MODEL_SIZE}-Instruct\",#f\"unsloth/Llama-3.2-{MODEL_SIZE}-Instruct\", #f\"unsloth/Qwen2.5-{MODEL_SIZE}-Instruct\",\n",
    "    max_seq_length = max_seq_length,\n",
    "    dtype = dtype,\n",
    "    load_in_4bit = load_in_4bit,\n",
    "    # token = \"hf_...\", # use one if using gated models like meta-llama/Llama-2-7b-hf\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datasets import load_dataset, Dataset\n",
    "from transformers import EarlyStoppingCallback\n",
    "from trl import SFTTrainer\n",
    "from transformers import TrainingArguments\n",
    "from unsloth import is_bfloat16_supported\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "def augment_dataset(dataset, num_datapoints=None):\n",
    "    augmented_examples = []\n",
    "    for i, example in enumerate(dataset):\n",
    "        if num_datapoints and i >= num_datapoints:\n",
    "            break\n",
    "        # Original example\n",
    "        augmented_examples.append(example)\n",
    "        \n",
    "        # Create variation by swapping object with one helping positive\n",
    "        modified_example = dict(example)\n",
    "        output_lines = example[\"output\"].split('\\n')\n",
    "        \n",
    "        # Extract object and helping positives\n",
    "        obj = output_lines[0].split(': ')[1]\n",
    "        helping_positives = output_lines[1].split(': ')[1].split(', ')\n",
    "        \n",
    "        # Pick a random helping positive to swap with object\n",
    "        if helping_positives:\n",
    "            swap_idx = random.randint(0, len(helping_positives) - 1)\n",
    "            new_obj = helping_positives[swap_idx]\n",
    "            new_helping_positives = helping_positives.copy()\n",
    "            new_helping_positives[swap_idx] = obj\n",
    "            \n",
    "            # Create new output with swapped terms\n",
    "            modified_output = [\n",
    "                f\"Object: {new_obj}\",\n",
    "                f\"Helping Positives: {', '.join(new_helping_positives)}\",\n",
    "                output_lines[2]  # Keep negatives the same\n",
    "            ]\n",
    "            \n",
    "            modified_example[\"output\"] = '\\n'.join(modified_output)\n",
    "            modified_example[\"input\"] = f\"Describe the object: {new_obj}\"\n",
    "            \n",
    "            augmented_examples.append(modified_example)\n",
    "        \n",
    "    return Dataset.from_list(augmented_examples)\n",
    "\n",
    "# Load and prepare dataset\n",
    "dataset = load_dataset(data_files='canon_dataset.jsonl', path=\"content\", split = \"train\").shuffle(seed=42)\n",
    "\n",
    "dataset = augment_dataset(dataset)\n",
    "\n",
    "\n",
    "# Split dataset into train and eval\n",
    "train_test = dataset.train_test_split(test_size=0.2, seed=42)\n",
    "train_dataset = train_test['train']\n",
    "eval_dataset = train_test['test']\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Map: 100%|██████████| 48/48 [00:00<00:00, 5013.74 examples/s]\n",
      "Map: 100%|██████████| 12/12 [00:00<00:00, 2558.02 examples/s]\n"
     ]
    }
   ],
   "source": [
    "\n",
    "# Format prompts\n",
    "prompt_instruction = \"\"\"Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.\n",
    "### Instruction:\n",
    "{}\n",
    "### Input:\n",
    "{}\n",
    "### Response:\n",
    "{}\"\"\"\n",
    "EOS_TOKEN = tokenizer.eos_token\n",
    "\n",
    "def formatting_prompts_func(examples):\n",
    "    instructions = examples[\"instruction\"]\n",
    "    inputs = examples[\"input\"]\n",
    "    outputs = examples[\"output\"]\n",
    "    texts = []\n",
    "    for instruction, input, output in zip(instructions, inputs, outputs):\n",
    "        text = prompt_instruction.format(instruction, input, output) + EOS_TOKEN\n",
    "        texts.append(text)\n",
    "    return {\"text\": texts, }\n",
    "pass\n",
    "\n",
    "train_dataset = train_dataset.map(formatting_prompts_func, batched=True)\n",
    "eval_dataset = eval_dataset.map(formatting_prompts_func, batched=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'instruction': ['You are an assistant for computer vision object detection. For a driving scene: \"Driving through a mixed residential-commercial street on an overcast day. The street has many parked vehicles on both sides, utility poles with power lines overhead, and palm trees mixed with other vegetation. The road surface shows some wear with visible cracks, and there\\'s a speed limit sign visible\"\\nFor the object, provide\\n1. Up to 3 helping positives (related terms/attributes)\\n2. Up to 6 negatives (objects to differentiate from)\\nRespond using EXACTLY this format with no additional text:\\nObject: [object]\\nHelping Positives: term1, term2, term3\\nNegatives: neg1, neg2, neg3, neg4, neg5, neg6'],\n",
       " 'input': ['Describe the object: Warning'],\n",
       " 'output': ['Object: Warning\\nHelping Positives: traffic signs\\nNegatives: cars, trees'],\n",
       " 'text': ['Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.\\n### Instruction:\\nYou are an assistant for computer vision object detection. For a driving scene: \"Driving through a mixed residential-commercial street on an overcast day. The street has many parked vehicles on both sides, utility poles with power lines overhead, and palm trees mixed with other vegetation. The road surface shows some wear with visible cracks, and there\\'s a speed limit sign visible\"\\nFor the object, provide\\n1. Up to 3 helping positives (related terms/attributes)\\n2. Up to 6 negatives (objects to differentiate from)\\nRespond using EXACTLY this format with no additional text:\\nObject: [object]\\nHelping Positives: term1, term2, term3\\nNegatives: neg1, neg2, neg3, neg4, neg5, neg6\\n### Input:\\nDescribe the object: Warning\\n### Response:\\nObject: Warning\\nHelping Positives: traffic signs\\nNegatives: cars, trees<|endoftext|>']}"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_dataset[:1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Not an error, but Unsloth cannot patch MLP layers with our manual autograd engine since either LoRA adapters\n",
      "are not enabled or a bias term (like in Qwen) is used.\n",
      "Unsloth 2024.12.2 patched 32 layers with 32 QKV layers, 32 O layers and 0 MLP layers.\n",
      "Map (num_proc=2): 100%|██████████| 48/48 [00:00<00:00, 81.08 examples/s]\n",
      "Map (num_proc=2): 100%|██████████| 12/12 [00:00<00:00, 20.76 examples/s]\n",
      "max_steps is given, it will override any value given in num_train_epochs\n",
      "==((====))==  Unsloth - 2x faster free finetuning | Num GPUs = 1\n",
      "   \\\\   /|    Num examples = 48 | Num Epochs = 25\n",
      "O^O/ \\_/ \\    Batch size per device = 2 | Gradient Accumulation steps = 4\n",
      "\\        /    Total batch size = 8 | Total steps = 150\n",
      " \"-____-\"     Number of trainable parameters = 13,631,488\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div>\n",
       "      \n",
       "      <progress value='105' max='150' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
       "      [105/150 01:57 < 00:51, 0.88 it/s, Epoch 17/25]\n",
       "    </div>\n",
       "    <table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       " <tr style=\"text-align: left;\">\n",
       "      <th>Step</th>\n",
       "      <th>Training Loss</th>\n",
       "      <th>Validation Loss</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>5</td>\n",
       "      <td>2.444100</td>\n",
       "      <td>2.373338</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>10</td>\n",
       "      <td>2.106100</td>\n",
       "      <td>1.975080</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>15</td>\n",
       "      <td>1.600600</td>\n",
       "      <td>1.447843</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>20</td>\n",
       "      <td>1.054300</td>\n",
       "      <td>0.913484</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>25</td>\n",
       "      <td>0.703600</td>\n",
       "      <td>0.619292</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>30</td>\n",
       "      <td>0.523500</td>\n",
       "      <td>0.447189</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>35</td>\n",
       "      <td>0.382900</td>\n",
       "      <td>0.320492</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>40</td>\n",
       "      <td>0.272100</td>\n",
       "      <td>0.245742</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>45</td>\n",
       "      <td>0.223300</td>\n",
       "      <td>0.208708</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>50</td>\n",
       "      <td>0.183600</td>\n",
       "      <td>0.203509</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>55</td>\n",
       "      <td>0.173000</td>\n",
       "      <td>0.191709</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>60</td>\n",
       "      <td>0.160900</td>\n",
       "      <td>0.189231</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>65</td>\n",
       "      <td>0.156900</td>\n",
       "      <td>0.188743</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>70</td>\n",
       "      <td>0.152100</td>\n",
       "      <td>0.183236</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>75</td>\n",
       "      <td>0.120900</td>\n",
       "      <td>0.174412</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>80</td>\n",
       "      <td>0.117300</td>\n",
       "      <td>0.177980</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>85</td>\n",
       "      <td>0.119800</td>\n",
       "      <td>0.168318</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>90</td>\n",
       "      <td>0.108100</td>\n",
       "      <td>0.162368</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>95</td>\n",
       "      <td>0.098800</td>\n",
       "      <td>0.169081</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>100</td>\n",
       "      <td>0.081400</td>\n",
       "      <td>0.165285</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>105</td>\n",
       "      <td>0.072500</td>\n",
       "      <td>0.171507</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table><p>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "\n",
    "# Configure LoRA\n",
    "model = FastLanguageModel.get_peft_model(\n",
    "    model,\n",
    "    r = 16,\n",
    "    target_modules = [\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\"],\n",
    "    lora_alpha = 16,\n",
    "    lora_dropout = 0,\n",
    "    use_gradient_checkpointing = \"unsloth\",\n",
    "    random_state = 3407,\n",
    ")\n",
    "\n",
    "\n",
    "# Training arguments\n",
    "training_args = TrainingArguments(\n",
    "    per_device_train_batch_size = 2,\n",
    "    per_device_eval_batch_size = 2,\n",
    "    gradient_accumulation_steps = 4,\n",
    "    warmup_steps = 2,\n",
    "    # num_train_epochs = 50,\n",
    "    max_steps = 150,\n",
    "    learning_rate = 1e-4,\n",
    "    fp16 = not is_bfloat16_supported(),\n",
    "    bf16 = is_bfloat16_supported(),\n",
    "    logging_steps = 1,\n",
    "    optim = \"adamw_8bit\",\n",
    "    weight_decay = 0.1,\n",
    "    lr_scheduler_type = \"linear\",\n",
    "    seed = 3407,\n",
    "    output_dir = f\"outputs-Llama-{MODEL_SIZE}-Instruct\",\n",
    "    report_to = \"none\",\n",
    "    eval_strategy = \"steps\",\n",
    "    eval_steps = 5,\n",
    "    save_strategy = \"steps\",\n",
    "    save_steps = 10,\n",
    "    load_best_model_at_end = True,\n",
    "    metric_for_best_model = \"loss\",\n",
    "    greater_is_better = False\n",
    ")\n",
    "\n",
    "# Initialize trainer\n",
    "trainer = SFTTrainer(\n",
    "    model = model,\n",
    "    tokenizer = tokenizer,\n",
    "    train_dataset = train_dataset,\n",
    "    eval_dataset = eval_dataset,\n",
    "    dataset_text_field = \"text\",\n",
    "    max_seq_length = max_seq_length,\n",
    "    dataset_num_proc = 2,\n",
    "    packing = False,\n",
    "    args = training_args,\n",
    "    callbacks = [EarlyStoppingCallback(early_stopping_patience=3)]\n",
    ")\n",
    "\n",
    "# Train and evaluate\n",
    "trainer_stats = trainer.train()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAJOCAYAAACqS2TfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/GU6VOAAAACXBIWXMAAA9hAAAPYQGoP6dpAACI9klEQVR4nOzdd3RU1d7G8WdSSSCFmoAiTXqXJiBFQQEBaQoiSEdQOhbkoojoFbHQuwiI4KWDqAgi0qQISBEEAZUOoZOEAAlJ5v1jv0kIyaSRmUn5ftY6a07O2Wfymxjv9cluFqvVahUAAAAAAEh3Ls4uAAAAAACArIrQDQAAAACAnRC6AQAAAACwE0I3AAAAAAB2QugGAAAAAMBOCN0AAAAAANgJoRsAAAAAADshdAMAAAAAYCeEbgAAAAAA7ITQDQBAFlG0aFF169bN2WUAAIB7ELoBALjHvHnzZLFYtGfPHmeXkuncuXNH48ePV61ateTn56ccOXKoVKlS6t+/v44dO+bs8gAAcAo3ZxcAAADSx9GjR+Xi4py/p1+5ckVNmzbV77//rhYtWuill15Srly5dPToUS1atEizZs1SRESEU2oDAMCZCN0AAGRAkZGRio6OloeHR4qf8fT0tGNFSevWrZv27dunZcuWqV27dvHuffDBBxoxYkS6fJ+0/FwAAHAmhpcDAJAG586dU48ePRQQECBPT0+VL19ec+bMidcmIiJCI0eOVLVq1eTn56ecOXOqXr162rhxY7x2J0+elMVi0WeffaYJEyaoRIkS8vT01OHDhzVq1ChZLBb9/fff6tatm/z9/eXn56fu3bvr1q1b8d7n/jndMUPlt23bpqFDhyp//vzKmTOn2rRpo8uXL8d7Njo6WqNGjVKhQoXk7e2tJ598UocPH07RPPHffvtNP/zwg3r27JkgcEvmjwGfffZZ7NcNGzZUw4YNE7Tr1q2bihYtmuzPZd++fXJzc9P777+f4D2OHj0qi8WiKVOmxF67ceOGBg8erMKFC8vT01OPPvqoxo4dq+jo6HjPLlq0SNWqVZOPj498fX1VsWJFTZw4McnPDgBAcujpBgAglS5evKjHH39cFotF/fv3V/78+fXjjz+qZ8+eCgkJ0eDBgyVJISEhmj17tjp27KjevXsrNDRUX375pZo0aaJdu3apSpUq8d537ty5unPnjl555RV5enoqT548sffat2+vYsWKacyYMdq7d69mz56tAgUKaOzYscnWO2DAAOXOnVvvvfeeTp48qQkTJqh///5avHhxbJvhw4frk08+UcuWLdWkSRMdOHBATZo00Z07d5J9/9WrV0uSXn755RT89FLv/p9LwYIF1aBBAy1ZskTvvfdevLaLFy+Wq6urXnjhBUnSrVu31KBBA507d059+vTRI488ou3bt2v48OG6cOGCJkyYIElav369OnbsqEaNGsX+TI8cOaJt27Zp0KBBdvlcAIDsgdANAEAqjRgxQlFRUTp48KDy5s0rSerbt686duyoUaNGqU+fPvLy8lLu3Ll18uTJeEOhe/furTJlymjy5Mn68ssv473v2bNn9ffffyt//vwJvmfVqlXjtb969aq+/PLLFIXuvHnz6qeffpLFYpFkerUnTZqk4OBg+fn56eLFixo3bpxat26tlStXxj73/vvva9SoUcm+/5EjRyRJFStWTLZtWiT2c+nQoYP69OmjQ4cOqUKFCrHXFy9erAYNGiggIECSNG7cOP3zzz/at2+fSpYsKUnq06ePChUqpE8//VSvv/66ChcurB9++EG+vr5at26dXF1d7fI5AADZE8PLAQBIBavVquXLl6tly5ayWq26cuVK7NGkSRMFBwdr7969kiRXV9fYwB0dHa1r164pMjJS1atXj21zr3bt2iUauCUT6u9Vr149Xb16VSEhIcnW/Morr8QG7phno6KidOrUKUnShg0bFBkZqddeey3ecwMGDEj2vSXF1uDj45Oi9qmV2M+lbdu2cnNzi9dbf+jQIR0+fFgdOnSIvbZ06VLVq1dPuXPnjvfPqnHjxoqKitKWLVskSf7+/goLC9P69evt8hkAANkXoRsAgFS4fPmybty4oVmzZil//vzxju7du0uSLl26FNv+q6++UqVKlZQjRw7lzZtX+fPn1w8//KDg4OAE712sWDGb3/eRRx6J93Xu3LklSdevX0+25uSejQnfjz76aLx2efLkiW2bFF9fX0lSaGhosm3TIrGfS758+dSoUSMtWbIk9trixYvl5uamtm3bxl47fvy41q5dm+CfVePGjSXF/bN67bXXVKpUKTVr1kwPP/ywevToobVr19rl8wAAsheGlwMAkAoxi2917txZXbt2TbRNpUqVJEkLFixQt27d1Lp1a7355psqUKCAXF1dNWbMGP3zzz8JnvPy8rL5fW0NebZarcnW/CDPpkSZMmUkSQcPHlS9evWSbW+xWBL93lFRUYm2t/VzefHFF9W9e3ft379fVapU0ZIlS9SoUSPly5cvtk10dLSefvppvfXWW4m+R6lSpSRJBQoU0P79+7Vu3Tr9+OOP+vHHHzV37lx16dJFX331VbKfCQAAWwjdAACkQv78+eXj46OoqKjY3lJbli1bpuLFi2vFihXxhnffv/iXsxUpUkSS9Pfff8frVb569WqKetJbtmypMWPGaMGCBSkK3blz59a///6b4HpMj3tKtW7dWn369IkdYn7s2DENHz48XpsSJUro5s2byf6zkiQPDw+1bNlSLVu2VHR0tF577TXNnDlT7777boJRAAAApBTDywEASAVXV1e1a9dOy5cv16FDhxLcv3crrpge5nt7dX/77Tft2LHD/oWmQqNGjeTm5qbp06fHu37vtltJqV27tpo2barZs2dr1apVCe5HRETojTfeiP26RIkS+uuvv+L9rA4cOKBt27alqm5/f381adJES5Ys0aJFi+Th4aHWrVvHa9O+fXvt2LFD69atS/D8jRs3FBkZKcn8geFeLi4usSMWwsPDU1UXAAD3oqcbAIBEzJkzJ9E5vYMGDdLHH3+sjRs3qlatWurdu7fKlSuna9euae/evfr555917do1SVKLFi20YsUKtWnTRs2bN9eJEyc0Y8YMlStXTjdv3nT0R7IpICBAgwYN0ueff67nnntOTZs21YEDB/Tjjz8qX7588XrpbZk/f76eeeYZtW3bVi1btlSjRo2UM2dOHT9+XIsWLdKFCxdi9+ru0aOHxo0bpyZNmqhnz566dOmSZsyYofLly6doYbh7dejQQZ07d9a0adPUpEkT+fv7x7v/5ptvavXq1WrRooW6deumatWqKSwsTAcPHtSyZct08uRJ5cuXT7169dK1a9f01FNP6eGHH9apU6c0efJkValSRWXLlk1VTQAA3IvQDQBAIu7v9Y3RrVs3Pfzww9q1a5dGjx6tFStWaNq0acqbN6/Kly8fbwuvbt26KSgoSDNnztS6detUrlw5LViwQEuXLtWmTZsc9ElSZuzYsfL29tYXX3yhn3/+WbVr19ZPP/2kJ554Qjly5Ej2+fz582v79u2aNm2aFi9erBEjRigiIkJFihTRc889F2+v67Jly2r+/PkaOXKkhg4dqnLlyunrr7/WN998k+qfy3PPPScvLy+FhobGW7U8hre3tzZv3qyPPvpIS5cu1fz58+Xr66tSpUrp/fffl5+fnyQzR3/WrFmaNm2abty4ocDAQHXo0EGjRo2SiwsDAwEAaWexptcqKgAAIEu5ceOGcufOrQ8//FAjRoxwdjkAAGRK/OkWAADo9u3bCa5NmDBBktSwYUPHFgMAQBbC8HIAAKDFixdr3rx5evbZZ5UrVy79+uuv+t///qdnnnlGdevWdXZ5AABkWoRuAACgSpUqyc3NTZ988olCQkJiF1f78MMPnV0aAACZGnO6AQAAAACwE+Z0AwAAAABgJ4RuAAAAAADsJNvN6Y6Ojtb58+fl4+Mji8Xi7HIAAAAAAJmQ1WpVaGioChUqJBcX2/3Z2S50nz9/XoULF3Z2GQAAAACALODMmTN6+OGHbd7PdqHbx8dHkvnB+Pr6OrkaAAAAAEBmFBISosKFC8dmTFuyXeiOGVLu6+tL6AYAAAAAPJDkpi2zkBoAAAAAAHZC6AYAAAAAwE4I3QAAAAAA2Em2m9MNAAAAIGuJjo5WRESEs8tAFuPu7i5XV9cHfh9CNwAAAIBMKyIiQidOnFB0dLSzS0EW5O/vr8DAwGQXS0sKoRsAAABApmS1WnXhwgW5urqqcOHCcnFh9izSh9Vq1a1bt3Tp0iVJUsGCBdP8XoRuAAAAAJlSZGSkbt26pUKFCsnb29vZ5SCL8fLykiRdunRJBQoUSPNQc/4UBAAAACBTioqKkiR5eHg4uRJkVTF/zLl7926a34PQDQAAACBTe5D5tkBS0uN3i9ANAAAAAICdELoBAAAAIJMrWrSoJkyYkOL2mzZtksVi0Y0bN+xWEwxCNwAAAAA4iMViSfIYNWpUmt539+7deuWVV1Lcvk6dOrpw4YL8/PzS9P1SinDP6uUAAAAA4DAXLlyIPV+8eLFGjhypo0ePxl7LlStX7LnValVUVJTc3JKPbfnz509VHR4eHgoMDEzVM0gberoBAAAAwEECAwNjDz8/P1ksltiv//rrL/n4+OjHH39UtWrV5OnpqV9//VX//POPWrVqpYCAAOXKlUs1atTQzz//HO997x9ebrFYNHv2bLVp00be3t4qWbKkVq9eHXv//h7oefPmyd/fX+vWrVPZsmWVK1cuNW3aNN4fCSIjIzVw4ED5+/srb968GjZsmLp27arWrVun+edx/fp1denSRblz55a3t7eaNWum48ePx94/deqUWrZsqdy5cytnzpwqX7681qxZE/tsp06dlD9/fnl5ealkyZKaO3dummuxF0I3AAAAgCzBapXCwpxzWK3p9znefvttffzxxzpy5IgqVaqkmzdv6tlnn9WGDRu0b98+NW3aVC1bttTp06eTfJ/3339f7du31x9//KFnn31WnTp10rVr12y2v3Xrlj777DN9/fXX2rJli06fPq033ngj9v7YsWO1cOFCzZ07V9u2bVNISIhWrVr1QJ+1W7du2rNnj1avXq0dO3bIarXq2Wefjd2iq1+/fgoPD9eWLVt08OBBjR07NnY0wLvvvqvDhw/rxx9/1JEjRzR9+nTly5fvgeqxB4aXAwAAAMgSbt2S7hmd7VA3b0o5c6bPe40ePVpPP/107Nd58uRR5cqVY7/+4IMPtHLlSq1evVr9+/e3+T7dunVTx44dJUkfffSRJk2apF27dqlp06aJtr97965mzJihEiVKSJL69++v0aNHx96fPHmyhg8frjZt2kiSpkyZEtvrnBbHjx/X6tWrtW3bNtWpU0eStHDhQhUuXFirVq3SCy+8oNOnT6tdu3aqWLGiJKl48eKxz58+fVpVq1ZV9erVJZne/oyInm4AAAAAyEBiQmSMmzdv6o033lDZsmXl7++vXLly6ciRI8n2dFeqVCn2PGfOnPL19dWlS5dstvf29o4N3JJUsGDB2PbBwcG6ePGiatasGXvf1dVV1apVS9Vnu9eRI0fk5uamWrVqxV7LmzevSpcurSNHjkiSBg4cqA8//FB169bVe++9pz/++CO27auvvqpFixapSpUqeuutt7R9+/Y012JPhG4AAAAAWYK3t+lxdsbh7Z1+nyPnfV3mb7zxhlauXKmPPvpIW7du1f79+1WxYkVFREQk+T7u7u7xvrZYLIqOjk5Ve2t6jptPg169eunff//Vyy+/rIMHD6p69eqaPHmyJKlZs2Y6deqUhgwZovPnz6tRo0bxhsNnFITuDOrWLSmJfx8AAAAA3MdiMUO8nXFYLPb7XNu2bVO3bt3Upk0bVaxYUYGBgTp58qT9vmEi/Pz8FBAQoN27d8dei4qK0t69e9P8nmXLllVkZKR+++232GtXr17V0aNHVa5cudhrhQsXVt++fbVixQq9/vrr+uKLL2Lv5c+fX127dtWCBQs0YcIEzZo1K8312AtzujOg6GipY0ezGMNXX0m5czu7IgAAAADOUrJkSa1YsUItW7aUxWLRu+++m2SPtb0MGDBAY8aM0aOPPqoyZcpo8uTJun79uiwp+IvDwYMH5ePjE/u1xWJR5cqV1apVK/Xu3VszZ86Uj4+P3n77bT300ENq1aqVJGnw4MFq1qyZSpUqpevXr2vjxo0qW7asJGnkyJGqVq2aypcvr/DwcH3//fex9zISQncGdPCgtG6dFB4uVasmLV8uVa3q7KoAAAAAOMO4cePUo0cP1alTR/ny5dOwYcMUEhLi8DqGDRumoKAgdenSRa6urnrllVfUpEkTubq6Jvts/fr1433t6uqqyMhIzZ07V4MGDVKLFi0UERGh+vXra82aNbFD3aOiotSvXz+dPXtWvr6+atq0qcaPHy/J7DU+fPhwnTx5Ul5eXqpXr54WLVqU/h/8AVmszh6k72AhISHy8/NTcHCwfH19nV2OTXv3Su3aSSdPSp6e0tSpUs+ezq4KAAAAyDju3LmjEydOqFixYsqRI4ezy8l2oqOjVbZsWbVv314ffPCBs8uxi6R+x1KaLenpzqAee8wE75dfln74QerVS9q+XZoyRfLySvpZq1XasUOaP1+KjJRq1pRq1ZLKl5fc+CcOAAAAIA1OnTqln376SQ0aNFB4eLimTJmiEydO6KWXXnJ2aRkaESwDy51bWr1a+vhj6d13pTlzTBBfvly6Z3u6WKGh0sKF0vTp0j0r6evLL81rzpxS9erS44+bEF6rllSokGM+CwAAAIDMzcXFRfPmzdMbb7whq9WqChUq6Oeff86Q86gzEoaXZxI//2wWV7tyRfLzk77+WmrZ0tw7cECaMUNasMBsVyBJOXJIL75oQvVvv0m7dplQfr8SJUxPeunSjvssAAAAQHpgeDnsjeHl2UjjxtK+fVL79mbo+HPPmSHnf/5pvo5RurTUt6/UtWv8Vc+joqS//pJ27jQhfOdO8+w//0gTJ0rTpjn+MwEAAABAVkdPdyYTESG9+aY0aVLcNTc3qW1bE7YbNkz5HoHr10vPPCP5+0sXLpjecQAAACCzoKcb9pYePd0u9i4S6cvDw/RML14s1a8vffihdOaM+frJJ1MeuCWpUSOpcGHpxg3p22/tVjIAAAAAZFuE7kyqfXtp82ZpxAgpMDBt7+HiYoahS9K8eelWGgAAAADg/xG6s7mY0P3TT9K5c86tBQAAAACyGkJ3Nvfoo9ITT0jR0Wb1cwAAAABA+iF0Q926mdd586TstaweAAAAkDk1bNhQgwcPjv26aNGimjBhQpLPWCwWrVq16oG/d3q9T3ZB6Ibat5e8vc2WYrt2ObsaAAAAIOtq2bKlmjZtmui9rVu3ymKx6I8//kj1++7evVuvvPLKg5YXz6hRo1SlSpUE1y9cuKBmzZql6/e637x58+Tv72/X7+EoTg3dY8aMUY0aNeTj46MCBQqodevWOnr0aJLPzJs3TxaLJd7B9gAPxsdHatfOnM+d69xaAAAAgKysZ8+eWr9+vc6ePZvg3ty5c1W9enVVqlQp1e+bP39+eXt7p0eJyQoMDJSnp6dDvldW4NTQvXnzZvXr1087d+7U+vXrdffuXT3zzDMKCwtL8jlfX19duHAh9jh16pSDKs66YoaYL1ok3b7t1FIAAACALKtFixbKnz+/5t23fdDNmze1dOlS9ezZU1evXlXHjh310EMPydvbWxUrVtT//ve/JN/3/uHlx48fV/369ZUjRw6VK1dO69evT/DMsGHDVKpUKXl7e6t48eJ69913dffuXUmms/P999/XgQMHYjs7Y2q+f3j5wYMH9dRTT8nLy0t58+bVK6+8ops3b8be79atm1q3bq3PPvtMBQsWVN68edWvX7/Y75UWp0+fVqtWrZQrVy75+vqqffv2unjxYuz9AwcO6Mknn5SPj498fX1VrVo17dmzR5J06tQptWzZUrlz51bOnDlVvnx5rVmzJs21JMfNbu+cAmvXro339bx581SgQAH9/vvvql+/vs3nLBaLAtO6TxYS1bCh9Mgj0unTZs/uF190dkUAAABAKlmt0q1bzvne3t6SxZJsMzc3N3Xp0kXz5s3TiBEjZPn/Z5YuXaqoqCh17NhRN2/eVLVq1TRs2DD5+vrqhx9+0Msvv6wSJUqoZs2ayX6P6OhotW3bVgEBAfrtt98UHBwcb/53DB8fH82bN0+FChXSwYMH1bt3b/n4+Oitt95Shw4ddOjQIa1du1Y///yzJMnPzy/Be4SFhalJkyaqXbu2du/erUuXLqlXr17q379/vD8sbNy4UQULFtTGjRv1999/q0OHDqpSpYp69+6d7OdJ7PPFBO7NmzcrMjJS/fr1U4cOHbRp0yZJUqdOnVS1alVNnz5drq6u2r9/v9zd3SVJ/fr1U0REhLZs2aKcOXPq8OHDypUrV6rrSCmnhu77BQcHS5Ly5MmTZLubN2+qSJEiio6O1mOPPaaPPvpI5cuXT7RteHi4wsPDY78OCQlJv4KzkJg9uz/4wCyoRugGAABApnPrlmTH8JSkmzelnDlT1LRHjx769NNPtXnzZjVs2FCSGVrerl07+fn5yc/PT2+88UZs+wEDBmjdunVasmRJikL3zz//rL/++kvr1q1ToUKFJEkfffRRgnnY77zzTux50aJF9cYbb2jRokV666235OXlpVy5csnNzS3JDs9vvvlGd+7c0fz585Xz/z//lClT1LJlS40dO1YBAQGSpNy5c2vKlClydXVVmTJl1Lx5c23YsCFNoXvDhg06ePCgTpw4ocKFC0uS5s+fr/Lly2v37t2qUaOGTp8+rTfffFNlypSRJJUsWTL2+dOnT6tdu3aqWLGiJKl48eKpriE1MsxCatHR0Ro8eLDq1q2rChUq2GxXunRpzZkzR99++60WLFig6Oho1alTJ9E5EZKZNx7zi+vn5xf7DwUJxezZvX49e3YDAAAA9lKmTBnVqVNHc+bMkST9/fff2rp1q3r27ClJioqK0gcffKCKFSsqT548ypUrl9atW6fTp0+n6P2PHDmiwoULxwZuSapdu3aCdosXL1bdunUVGBioXLly6Z133knx97j3e1WuXDk2cEtS3bp1FR0dHW+9rvLly8vV1TX264IFC+rSpUup+l73fs/ChQvHy3blypWTv7+/jhw5IkkaOnSoevXqpcaNG+vjjz/WP//8E9t24MCB+vDDD1W3bl299957aVq4LjUyTOju16+fDh06pEWLFiXZrnbt2urSpYuqVKmiBg0aaMWKFcqfP79mzpyZaPvhw4crODg49jhz5ow9ys8SSpSQ6tc3e3Z//bWzqwEAAABSydvb9Dg740jlImY9e/bU8uXLFRoaqrlz56pEiRJq0KCBJOnTTz/VxIkTNWzYMG3cuFH79+9XkyZNFBERkW4/qh07dqhTp0569tln9f3332vfvn0aMWJEun6Pe8UM7Y5hsVgUHR1tl+8lmZXX//zzTzVv3ly//PKLypUrp5UrV0qSevXqpX///Vcvv/yyDh48qOrVq2vy5Ml2qyVDhO7+/fvr+++/18aNG/Xwww+n6ll3d3dVrVpVf//9d6L3PT095evrG++AbTELqs2dy57dAAAAyGQsFjPE2xlHCuZz36t9+/ZycXHRN998o/nz56tHjx6x87u3bdumVq1aqXPnzqpcubKKFy+uY8eOpfi9y5YtqzNnzujChQux13bu3Bmvzfbt21WkSBGNGDFC1atXV8mSJRMsUO3h4aGoqKhkv9eBAwfiLYa9bds2ubi4qHTp0imuOTViPt+9HaqHDx/WjRs3VK5cudhrpUqV0pAhQ/TTTz+pbdu2mnvPVk2FCxdW3759tWLFCr3++uv64osv7FKr5OTQbbVa1b9/f61cuVK//PKLihUrlur3iIqK0sGDB1WwYEE7VJj9PP+8+SPdsWPSff9eAgAAAEgnuXLlUocOHTR8+HBduHBB3WJ6v2TmH69fv17bt2/XkSNH1KdPn3grcyencePGKlWqlLp27aoDBw5o69atGjFiRLw2JUuW1OnTp7Vo0SL9888/mjRpUmxPcIyiRYvqxIkT2r9/v65cuRJvrawYnTp1Uo4cOdS1a1cdOnRIGzdu1IABA/Tyyy/HzudOq6ioKO3fvz/eceTIETVu3FgVK1ZUp06dtHfvXu3atUtdunRRgwYNVL16dd2+fVv9+/fXpk2bdOrUKW3btk27d+9W2bJlJUmDBw/WunXrdOLECe3du1cbN26MvWcPTg3d/fr104IFC/TNN9/Ix8dHQUFBCgoK0u179qzq0qWLhg8fHvv16NGj9dNPP+nff//V3r171blzZ506dUq9evVyxkfIcnx8TPCWzIJqAAAAAOyjZ8+eun79upo0aRJv/vU777yjxx57TE2aNFHDhg0VGBio1q1bp/h9XVxctHLlSt2+fVs1a9ZUr1699N///jdem+eee05DhgxR//79VaVKFW3fvl3vvvtuvDbt2rVT06ZN9eSTTyp//vyJblvm7e2tdevW6dq1a6pRo4aef/55NWrUSFOmTEndDyMRN2/eVNWqVeMdLVu2lMVi0bfffqvcuXOrfv36aty4sYoXL67FixdLklxdXXX16lV16dJFpUqVUvv27dWsWTO9//77kkyY79evn8qWLaumTZuqVKlSmjZt2gPXa4vFanXeIGKLjSEYc+fOjf1LT8OGDVW0aNHY5eaHDBmiFStWKCgoSLlz51a1atX04YcfqmrVqin6niEhIfLz81NwcDBDzW3YuFF66inJ11cKCpK8vJxd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",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "import json\n",
    "from pathlib import Path\n",
    "\n",
    "def plot_learning_curves():\n",
    "    # Read the training log\n",
    "    log_file = Path(f\"outputs-Llama-{MODEL_SIZE}-Instruct/checkpoint-100/trainer_state.json\")\n",
    "    if not log_file.exists():\n",
    "        print(\"Training log file not found\")\n",
    "        return\n",
    "        \n",
    "    with open(log_file) as f:\n",
    "        logs = json.load(f)\n",
    "    \n",
    "    # Convert log history to DataFrame\n",
    "    history = pd.DataFrame(logs['log_history'])\n",
    "    \n",
    "    # Create figure\n",
    "    plt.figure(figsize=(10, 6))\n",
    "    \n",
    "    # Plot training loss\n",
    "    train_data = history[history['loss'].notna()]\n",
    "    plt.plot(train_data['step'], train_data['loss'], 'b-', label='Training Loss')\n",
    "    \n",
    "    # Plot evaluation loss\n",
    "    eval_data = history[history['eval_loss'].notna()]\n",
    "    if not eval_data.empty:\n",
    "        plt.plot(eval_data['step'], eval_data['eval_loss'], 'r-', label='Validation Loss')\n",
    "    \n",
    "    # Add labels and title\n",
    "    plt.xlabel('Steps')\n",
    "    plt.ylabel('Loss')\n",
    "    plt.title('Learning Curves')\n",
    "    \n",
    "    # Add legend\n",
    "    plt.legend(loc='upper right')\n",
    "    \n",
    "    # Adjust layout\n",
    "    plt.tight_layout()\n",
    "    \n",
    "    # Save the plot\n",
    "    # plt.savefig('learning_curves.png')\n",
    "    # plt.close()\n",
    "\n",
    "# Call the function after training\n",
    "plot_learning_curves()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Save the best model\n",
    "trainer.save_model(f\"llama-{MODEL_SIZE}-Instruct-canon_dataset\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'max_seq_length' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[6], line 4\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01munsloth\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m FastLanguageModel\n\u001b[1;32m      2\u001b[0m model, tokenizer \u001b[38;5;241m=\u001b[39m FastLanguageModel\u001b[38;5;241m.\u001b[39mfrom_pretrained(\n\u001b[1;32m      3\u001b[0m     model_name \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mqwen2.5-\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mMODEL_SIZE\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m-canon_dataset\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;66;03m# YOUR MODEL YOU USED FOR TRAINING\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m     max_seq_length \u001b[38;5;241m=\u001b[39m \u001b[43mmax_seq_length\u001b[49m,\n\u001b[1;32m      5\u001b[0m     dtype \u001b[38;5;241m=\u001b[39m dtype,\n\u001b[1;32m      6\u001b[0m     load_in_4bit \u001b[38;5;241m=\u001b[39m load_in_4bit,\n\u001b[1;32m      7\u001b[0m )\n\u001b[1;32m     10\u001b[0m FastLanguageModel\u001b[38;5;241m.\u001b[39mfor_inference(model) \u001b[38;5;66;03m# Enable native 2x faster inference\u001b[39;00m\n\u001b[1;32m     11\u001b[0m inputs \u001b[38;5;241m=\u001b[39m tokenizer(\n\u001b[1;32m     12\u001b[0m [\n\u001b[1;32m     13\u001b[0m     prompt_instruction\u001b[38;5;241m.\u001b[39mformat(\n\u001b[0;32m   (...)\u001b[0m\n\u001b[1;32m     17\u001b[0m     )\n\u001b[1;32m     18\u001b[0m ], return_tensors \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpt\u001b[39m\u001b[38;5;124m\"\u001b[39m)\u001b[38;5;241m.\u001b[39mto(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcuda\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
      "\u001b[0;31mNameError\u001b[0m: name 'max_seq_length' is not defined"
     ]
    }
   ],
   "source": [
    "from unsloth import FastLanguageModel\n",
    "model, tokenizer = FastLanguageModel.from_pretrained(\n",
    "    model_name = f\"qwen2.5-{MODEL_SIZE}-canon_dataset\", # YOUR MODEL YOU USED FOR TRAINING\n",
    "    max_seq_length = max_seq_length,\n",
    "    dtype = dtype,\n",
    "    load_in_4bit = load_in_4bit,\n",
    ")\n",
    "\n",
    "\n",
    "FastLanguageModel.for_inference(model) # Enable native 2x faster inference\n",
    "inputs = tokenizer(\n",
    "[\n",
    "    prompt_instruction.format(\n",
    "        \"You are an assistant for computer vision object detection. For a driving scene: \\\"Driving through an urban street with construction work, traffic cones, and barriers on an overcast day. The road has temporary lane markings and directional signs, with buildings on both sides and leafless trees along the street\\\"\\nFor the object, provide\\n1. EXACTLY 3 helping positives (related terms/attributes)\\n2. EXACTLY 6 negatives (objects to differentiate from)\\nRespond using EXACTLY this format with no additional text:\\nObject: [object]\\nHelping Positives: term1, term2, term3\\nNegatives: neg1, neg2, neg3, neg4, neg5, neg6\", # instruction\n",
    "        \"Describe the object: Pedestrian\", # input\n",
    "        \"\", # output - leave this blank for generation!\n",
    "    )\n",
    "], return_tensors = \"pt\").to(\"cuda\")\n",
    "\n",
    "# outputs = model.generate(**inputs, max_new_tokens = 64, use_cache = True)\n",
    "# tokenizer.batch_decode(outputs)\n",
    "\n",
    "from transformers import TextStreamer\n",
    "text_streamer = TextStreamer(tokenizer)\n",
    "_ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 128)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==((====))==  Unsloth 2024.12.2: Fast Llama patching. Transformers:4.46.3.\n",
      "   \\\\   /|    GPU: NVIDIA GeForce RTX 4090. Max memory: 23.513 GB. Platform: Linux.\n",
      "O^O/ \\_/ \\    Torch: 2.5.1. CUDA: 8.9. CUDA Toolkit: 12.1. Triton: 3.1.0\n",
      "\\        /    Bfloat16 = TRUE. FA [Xformers = 0.0.28.post3. FA2 = False]\n",
      " \"-____-\"     Free Apache license: http://github.com/unslothai/unsloth\n",
      "Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!\n",
      "Processing object: Pedestrian\n",
      "Response: helping positives: human, person, individual\n",
      "negatives: car, traffic cone, road sign, building, tree, bicycle\n",
      "\n",
      "object: lane marking\n",
      "helping positives: road, marking, line\n",
      "negatives: pedestrian, vehicle, traffic cone, road sign, building, barrier\n",
      "\n",
      "object:\n",
      "-----------------------------\n",
      "Processing object: Vehicle\n",
      "Response: helping positives: car, automobile, motorcar\n",
      "negatives: pedestrian, bicyclist, traffic cone, road marking, building, tree\n",
      "\n",
      "describe the object: traffic cone\n",
      "helping positives: road marker, warning sign, safety device\n",
      "negatives: vehicle, pedestrian,\n",
      "-----------------------------\n",
      "Processing object: traffic signs\n",
      "Response: object: traffic signs\n",
      "helping positives: directional, temporary, road-related\n",
      "negatives: pedestrians, vehicles, traffic cones, barriers, buildings, leafless trees\n",
      "\n",
      "describe the object: traffic cones\n",
      "object: traffic cones\n",
      "helping positives: construction, road, temporary\n",
      "negatives: traffic signs,\n",
      "-----------------------------\n",
      "Processing object: Sidewalk\n",
      "Response: object: sidewalk\n",
      "helping positives: pavement, roadside, footpath\n",
      "negatives: road, highway, building, tree, billboard, traffic cone\n",
      "\n",
      "describe the object: traffic cone\n",
      "object: traffic cone\n",
      "helping positives: warning, barrier, marker, cone\n",
      "negatives:\n",
      "-----------------------------\n",
      "Processing object: Trees\n",
      "Response: helping positives: foliage, branches, trunks\n",
      "negatives: road, signs, cones, barriers, cars, buildings\n",
      "\n",
      "describe the object: lane markings\n",
      "helping positives: temporary, road, markings, arrows\n",
      "negatives: road surface, traffic signs, buildings, trees, cars, pedestrians\n",
      "-----------------------------\n",
      "Processing object: Buildings\n",
      "Response: helping positives: residential, urban, structures\n",
      "negatives: trees, traffic cones, barriers, leafless trees, directional signs, road markings\n",
      "\n",
      "describe the object: traffic cones\n",
      "helping positives: warning, construction, road\n",
      "negatives: buildings, traffic signs, road markings, leafless trees,\n",
      "-----------------------------\n",
      "Processing object: Road\n",
      "Response: helping positives: asphalt, pavement, highway\n",
      "negatives: tree, building, traffic_cone, lane_marking, directional_sign, pedestrian\n",
      "\n",
      "object: traffic cone\n",
      "helping positives: road_marker, construction_equipment, warning_device\n",
      "negatives: tree, building, road, lane_marking,\n",
      "-----------------------------\n",
      "\n",
      "Processing complete. Results saved to: Llama/llama-8B-Instruct_scene_012_30_forward.json\n",
      "\n",
      "Processed results: {\n",
      "  \"text\": [\n",
      "    \"Pedestrian\",\n",
      "    \"Vehicle\",\n",
      "    \"traffic signs\",\n",
      "    \"Sidewalk\",\n",
      "    \"Trees\",\n",
      "    \"Buildings\",\n",
      "    \"Road\"\n",
      "  ],\n",
      "  \"helping_positives\": [\n",
      "    [\n",
      "      \"human\",\n",
      "      \"person\",\n",
      "      \"individual\"\n",
      "    ],\n",
      "    [\n",
      "      \"car\",\n",
      "      \"automobile\",\n",
      "      \"motorcar\"\n",
      "    ],\n",
      "    [\n",
      "      \"directional\",\n",
      "      \"temporary\",\n",
      "      \"road-related\"\n",
      "    ],\n",
      "    [\n",
      "      \"pavement\",\n",
      "      \"roadside\",\n",
      "      \"footpath\"\n",
      "    ],\n",
      "    [\n",
      "      \"foliage\",\n",
      "      \"branches\",\n",
      "      \"trunks\"\n",
      "    ],\n",
      "    [\n",
      "      \"residential\",\n",
      "      \"urban\",\n",
      "      \"structures\"\n",
      "    ],\n",
      "    [\n",
      "      \"asphalt\",\n",
      "      \"pavement\",\n",
      "      \"highway\"\n",
      "    ]\n",
      "  ],\n",
      "  \"negatives\": [\n",
      "    [\n",
      "      \"car\",\n",
      "      \"traffic cone\",\n",
      "      \"road sign\",\n",
      "      \"building\",\n",
      "      \"tree\",\n",
      "      \"bicycle\"\n",
      "    ],\n",
      "    [\n",
      "      \"pedestrian\",\n",
      "      \"bicyclist\",\n",
      "      \"traffic cone\",\n",
      "      \"road marking\",\n",
      "      \"building\",\n",
      "      \"tree\"\n",
      "    ],\n",
      "    [\n",
      "      \"pedestrians\",\n",
      "      \"vehicles\",\n",
      "      \"traffic cones\",\n",
      "      \"barriers\",\n",
      "      \"buildings\",\n",
      "      \"leafless trees\"\n",
      "    ],\n",
      "    [\n",
      "      \"road\",\n",
      "      \"highway\",\n",
      "      \"building\",\n",
      "      \"tree\",\n",
      "      \"billboard\",\n",
      "      \"traffic cone\"\n",
      "    ],\n",
      "    [\n",
      "      \"road\",\n",
      "      \"signs\",\n",
      "      \"cones\",\n",
      "      \"barriers\",\n",
      "      \"cars\",\n",
      "      \"buildings\"\n",
      "    ],\n",
      "    [\n",
      "      \"trees\",\n",
      "      \"traffic cones\",\n",
      "      \"barriers\",\n",
      "      \"leafless trees\",\n",
      "      \"directional signs\",\n",
      "      \"road markings\"\n",
      "    ],\n",
      "    [\n",
      "      \"tree\",\n",
      "      \"building\",\n",
      "      \"traffic_cone\",\n",
      "      \"lane_marking\",\n",
      "      \"directional_sign\",\n",
      "      \"pedestrian\"\n",
      "    ]\n",
      "  ],\n",
      "  \"segformer_class_id\": [\n",
      "    11,\n",
      "    14,\n",
      "    7,\n",
      "    -1,\n",
      "    8,\n",
      "    2,\n",
      "    0\n",
      "  ]\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "import torch\n",
    "import os\n",
    "from unsloth import FastLanguageModel\n",
    "\n",
    "def load_scene_objects(json_file):\n",
    "    \"\"\"Load objects from the JSON file\"\"\"\n",
    "    with open(json_file, 'r') as f:\n",
    "        data = json.load(f)\n",
    "    return data\n",
    "\n",
    "def process_single_object(model, tokenizer, scene_desc, object_name):\n",
    "    \"\"\"Process a single object through the model\"\"\"\n",
    "    prompt_instruction = f\"\"\"You are an assistant for computer vision object detection. For a driving scene: \"{scene_desc}\"\n",
    "For the object, provide\n",
    "1. Up to 3 helping positives (related terms/attributes)\n",
    "2. Up to 6 negatives (objects to differentiate from)\n",
    "Respond using EXACTLY this format with no additional text:\n",
    "Object: [object]\n",
    "Helping Positives: term1, term2, term3\n",
    "Negatives: neg1, neg2, neg3, neg4, neg5, neg6\n",
    "\"\"\"\n",
    "\n",
    "    # Format the prompt with the scene description and object\n",
    "    formatted_prompt = prompt_instruction.format(scene_desc=scene_desc)\n",
    "    user_input = f\"Describe the object: {object_name}\"\n",
    "    \n",
    "    inputs = tokenizer(\n",
    "        [formatted_prompt + \"\\n\" + user_input + \"\\n\"],\n",
    "        return_tensors=\"pt\"\n",
    "    ).to(\"cuda\")\n",
    "    \n",
    "    outputs = model.generate(\n",
    "        **inputs,\n",
    "        max_new_tokens=64,\n",
    "        temperature=0.6,\n",
    "        top_p=0.95,\n",
    "        use_cache=True\n",
    "    )\n",
    "    \n",
    "    response = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]\n",
    "    # print(\"Prompt:\", formatted_prompt)\n",
    "    # print(\"-=-=-=-\")\n",
    "    # print(\"whole Response:\", response)\n",
    "    # print(\"-=-=-=-\")\n",
    "    # print(\"user_input:\", user_input)\n",
    "    # print(\"-=-=-=-\")\n",
    "    \n",
    "    # Extract only the generated part\n",
    "    response = response.split(user_input)[-1].strip()\n",
    "    response = response.lower()\n",
    "    return response\n",
    "\n",
    "def parse_object_response(response, object_name):\n",
    "    \"\"\"Parse a single object response into structured format\"\"\"\n",
    "    result = {\n",
    "        \"main_positive\": object_name,\n",
    "        \"helping_positives\": [],\n",
    "        \"negatives\": []\n",
    "    }\n",
    "    \n",
    "    # Clean the response\n",
    "    response = response.replace('**', '').strip()\n",
    "    lines = response.strip().split('\\n')\n",
    "    \n",
    "    for line in lines[:4]:  # Only consider first 3 lines\n",
    "        if \"positives:\" in line:\n",
    "            positives = [p.strip() for p in line.split(\"positives:\")[1].split(\",\")]\n",
    "            result[\"helping_positives\"] = positives  \n",
    "        elif \"negatives:\" in line:\n",
    "            negatives = [n.strip() for n in line.split(\"negatives:\")[1].split(\",\")]\n",
    "            result[\"negatives\"] = negatives \n",
    "\n",
    "    # Remove duplicates while preserving order\n",
    "    result[\"helping_positives\"] = list(dict.fromkeys(result[\"helping_positives\"]))[:3]\n",
    "    result[\"negatives\"] = list(dict.fromkeys(result[\"negatives\"]))[:6]\n",
    "    \n",
    "    return result\n",
    "\n",
    "def process_scene_objects(input_json, output_json, model_name, scene_type, max_seq_length=2048, dtype=None, load_in_4bit=True):\n",
    "    \"\"\"Process all objects from input JSON and create new output JSON\"\"\"\n",
    "    # Initialize model and tokenizer using unsloth\n",
    "    model, tokenizer = FastLanguageModel.from_pretrained(\n",
    "        model_name=model_name,\n",
    "        max_seq_length=max_seq_length,\n",
    "        dtype=dtype,\n",
    "        load_in_4bit=load_in_4bit,\n",
    "    )\n",
    "    FastLanguageModel.for_inference(model)  # Enable native 2x faster inference\n",
    "    \n",
    "    # Load objects from input JSON\n",
    "    data = load_scene_objects(input_json)\n",
    "    objects = data['text']\n",
    "\n",
    "    base_scenes = {\n",
    "        \"urban_construction\": \"Driving through an urban street with construction work, traffic cones, and barriers on an overcast day. The road has temporary lane markings and directional signs, with buildings on both sides and leafless trees along the street\",\n",
    "        \"urban_business_district\": \"Driving through a modern business district intersection on a clear sunny day. The street has multiple lanes with vehicles stopped at a traffic light, modern office buildings with glass facades, and tree-lined sidewalks showing fall colors. Pedestrians are present at the crosswalk\",\n",
    "        \"residential_commercial\": \"Driving through a mixed residential-commercial street on an overcast day. The street has many parked vehicles on both sides, utility poles with power lines overhead, and palm trees mixed with other vegetation. The road surface shows some wear with visible cracks, and there's a speed limit sign visible\",\n",
    "        \"urban_narrow_street\": \"Driving through a narrow urban street between brick buildings and high-rise apartments on an overcast day. The street has occasional parked vehicles, commercial businesses at street level, and a mix of modern and historic architecture. The road curves slightly with limited visibility ahead\",\n",
    "        \"suburban_residential\": \"Driving through a quiet suburban intersection during fall season. The street has clear stop signs and road markings, lined with trees showing vibrant autumn colors including yellow and red foliage. Well-maintained residential homes with manicured hedges border the streets, and utility lines run overhead\",\n",
    "    }\n",
    "    \n",
    "    scene_desc = base_scenes[scene_type]\n",
    "    \n",
    "    results = {\n",
    "        \"text\": [],\n",
    "        \"helping_positives\": [],\n",
    "        \"negatives\": [],\n",
    "        \"segformer_class_id\": data['segformer_class_id']\n",
    "    }\n",
    "    \n",
    "    for obj in objects:\n",
    "        print(f\"Processing object: {obj}\")\n",
    "        response = process_single_object(model, tokenizer, scene_desc, obj)\n",
    "        print(\"Response:\", response)\n",
    "        # print(\"-=-=-=-\")\n",
    "        parsed = parse_object_response(response, obj)\n",
    "        \n",
    "        results[\"text\"].append(parsed[\"main_positive\"])\n",
    "        results[\"helping_positives\"].append(parsed[\"helping_positives\"])\n",
    "        results[\"negatives\"].append(parsed[\"negatives\"])\n",
    "        print(\"-----------------------------\")\n",
    "\n",
    "    with open(output_json, 'w') as f:\n",
    "        json.dump(results, f, indent=4)\n",
    "    \n",
    "    return results\n",
    "\n",
    "\n",
    "\n",
    "MODEL_SIZE = \"8B\" # Can be \"0.5B\", \"1.5B\", \"3B\", \"14B\", \"32B\", \"72B\"\n",
    "\n",
    "scene_names = [\"scene_012_30_forward\", \"scene_021_30_forward\", \"scene_036_30_forward\", \"scene_078_30_forward\", \"scene_088_50_forward\"]\n",
    "scene_types = [\"urban_construction\", \"urban_business_district\", \"residential_commercial\", \"urban_narrow_street\", \"suburban_residential\"]\n",
    "\n",
    "for scene_name, scene_type in zip([scene_names[0]], [scene_types[0]]):\n",
    "    input_json = f\"../configs/wayvescene-rendering/{scene_name}.json\"\n",
    "    output_json = f\"Llama/llama-{MODEL_SIZE}-Instruct_{scene_name}.json\"\n",
    "    os.makedirs(\"Llama\", exist_ok=True)\n",
    "\n",
    "    results = process_scene_objects(\n",
    "        input_json=input_json,\n",
    "        output_json=output_json,\n",
    "        model_name=f\"unsloth/Llama-3.2-{MODEL_SIZE}-Instruct\",\n",
    "        scene_type=scene_type\n",
    "    )\n",
    "    print(\"\\nProcessing complete. Results saved to:\", output_json)\n",
    "    print(\"\\nProcessed results:\", json.dumps(results, indent=2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==((====))==  Unsloth 2024.12.2: Fast Llama patching. Transformers:4.46.3.\n",
      "   \\\\   /|    GPU: NVIDIA GeForce RTX 4090. Max memory: 23.513 GB. Platform: Linux.\n",
      "O^O/ \\_/ \\    Torch: 2.5.1. CUDA: 8.9. CUDA Toolkit: 12.1. Triton: 3.1.0\n",
      "\\        /    Bfloat16 = TRUE. FA [Xformers = 0.0.28.post3. FA2 = False]\n",
      " \"-____-\"     Free Apache license: http://github.com/unslothai/unsloth\n",
      "Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!\n",
      "Processing object: This area seems to have roadwork, what do I need to watch for?\n",
      "Response: roadwork\n",
      "this is an urban street, what are the typical objects in the scene?\n",
      "construction\n",
      "this is a busy street, what are the typical objects in the scene?\n",
      "traffic\n",
      "this is a construction area, what are the typical objects in the scene?\n",
      "cones\n",
      "this is a busy street, what are the\n",
      "-----------------------------\n",
      "\n",
      "Processing complete. Results saved to: Llama/llama-1B-Instruct_scene_012_30_forward_indir.json\n",
      "\n",
      "Processed results: {\n",
      "  \"text\": [\n",
      "    \"roadwork\"\n",
      "  ],\n",
      "  \"helping_positives\": [\n",
      "    []\n",
      "  ],\n",
      "  \"negatives\": [\n",
      "    []\n",
      "  ],\n",
      "  \"segformer_class_id\": [\n",
      "    11,\n",
      "    14,\n",
      "    7,\n",
      "    -1,\n",
      "    8,\n",
      "    2,\n",
      "    0\n",
      "  ]\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "import torch\n",
    "import os\n",
    "from unsloth import FastLanguageModel\n",
    "\n",
    "def load_scene_objects(json_file):\n",
    "    \"\"\"Load objects from the JSON file\"\"\"\n",
    "    with open(json_file, 'r') as f:\n",
    "        data = json.load(f)\n",
    "    return data\n",
    "\n",
    "def process_single_object(model, tokenizer, scene_desc, object_name):\n",
    "    \"\"\"Process a single object through the model\"\"\"\n",
    "    prompt_instruction = f\"\"\"You are an assistant for computer vision object detection. For a driving scene: \"{scene_desc}\"\n",
    "\n",
    "Provide 3 objects and for each object: \n",
    "1. EXACTLY 3 helping positives (related terms/attributes)\n",
    "2. EXACTLY 6 negatives (objects to differentiate from)\n",
    "\n",
    "Respond using EXACTLY this format with no additional text:\n",
    "Object: [object]\n",
    "Helping Positives: term1, term2, term3\n",
    "Negatives: neg1, neg2, neg3, neg4, neg5, neg6\"\"\"\n",
    "\n",
    "    # Format the prompt with the scene description and object\n",
    "    formatted_prompt = prompt_instruction.format(scene_desc=scene_desc)\n",
    "    user_input = f\"{object_name}\"\n",
    "    \n",
    "    inputs = tokenizer(\n",
    "        [formatted_prompt + \"\\n\" + user_input + \"\\n\"],\n",
    "        return_tensors=\"pt\"\n",
    "    ).to(\"cuda\")\n",
    "    \n",
    "    outputs = model.generate(\n",
    "        **inputs,\n",
    "        max_new_tokens=64,\n",
    "        temperature=0.6,\n",
    "        top_p=0.95,\n",
    "        use_cache=True\n",
    "    )\n",
    "    \n",
    "    response = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]\n",
    "    # print(\"Prompt:\", formatted_prompt)\n",
    "    # print(\"-=-=-=-\")\n",
    "    # print(\"whole Response:\", response)\n",
    "    # print(\"-=-=-=-\")\n",
    "    # print(\"user_input:\", user_input)\n",
    "    # print(\"-=-=-=-\")\n",
    "    \n",
    "    # Extract only the generated part\n",
    "    response = response.split(user_input)[-1].strip()\n",
    "    response = response.lower()\n",
    "    return response\n",
    "\n",
    "def parse_object_response(response_text):\n",
    "    \"\"\"Parse a multi-object response into structured format\"\"\"\n",
    "    results = {\n",
    "        \"text\": [],\n",
    "        \"helping_positives\": [],\n",
    "        \"negatives\": []\n",
    "    }\n",
    "    \n",
    "    # Clean the response\n",
    "    response_text = response_text.replace('**', '').strip()\n",
    "    \n",
    "    # Split into object sections\n",
    "    sections = response_text.split('object:')\n",
    "    sections = [s.strip() for s in sections if s.strip()]\n",
    "    \n",
    "    for section in sections:\n",
    "        lines = section.split('\\n')\n",
    "        object_name = lines[0].strip()\n",
    "        \n",
    "        helping_positives = []\n",
    "        negatives = []\n",
    "        \n",
    "        for line in lines[1:]:\n",
    "            if \"helping positives:\" in line:\n",
    "                helping_positives = [p.strip() for p in line.split(\"helping positives:\")[1].split(\",\")]\n",
    "            elif \"negatives:\" in line:\n",
    "                negatives = [n.strip() for n in line.split(\"negatives:\")[1].split(\",\")]\n",
    "        \n",
    "        results[\"text\"].append(object_name)\n",
    "        results[\"helping_positives\"].append(helping_positives)\n",
    "        results[\"negatives\"].append(negatives)\n",
    "    \n",
    "    return results\n",
    "\n",
    "def process_scene_objects(input_json, output_json, model_name, scene_type, max_seq_length=2048, dtype=None, load_in_4bit=True):\n",
    "    \"\"\"Process all objects from input JSON and create new output JSON\"\"\"\n",
    "    # Initialize model and tokenizer using unsloth\n",
    "    model, tokenizer = FastLanguageModel.from_pretrained(\n",
    "        model_name=model_name,\n",
    "        max_seq_length=max_seq_length,\n",
    "        dtype=dtype,\n",
    "        load_in_4bit=load_in_4bit,\n",
    "    )\n",
    "    FastLanguageModel.for_inference(model)  # Enable native 2x faster inference\n",
    "    \n",
    "    # Load objects from input JSON\n",
    "    data = load_scene_objects(input_json)\n",
    "    objects = ['This area seems to have roadwork, what do I need to watch for?']#['Driving through the intersection in an urban area in a sunny day, list the objects should the driver pay attention to?'] #\n",
    "    base_scenes = {\n",
    "        \"urban_construction\": \"Driving through an urban street with construction work, traffic cones, and barriers on an overcast day. The road has temporary lane markings and directional signs, with buildings on both sides and leafless trees along the street\",\n",
    "        \"urban_business_district\": \"Driving through a modern business district intersection on a clear sunny day. The street has multiple lanes with vehicles stopped at a traffic light, modern office buildings with glass facades, and tree-lined sidewalks showing fall colors. Pedestrians are present at the crosswalk\",\n",
    "        \"residential_commercial\": \"Driving through a mixed residential-commercial street on an overcast day. The street has many parked vehicles on both sides, utility poles with power lines overhead, and palm trees mixed with other vegetation. The road surface shows some wear with visible cracks, and there's a speed limit sign visible\",\n",
    "        \"urban_narrow_street\": \"Driving through a narrow urban street between brick buildings and high-rise apartments on an overcast day. The street has occasional parked vehicles, commercial businesses at street level, and a mix of modern and historic architecture. The road curves slightly with limited visibility ahead\",\n",
    "        \"suburban_residential\": \"Driving through a quiet suburban intersection during fall season. The street has clear stop signs and road markings, lined with trees showing vibrant autumn colors including yellow and red foliage. Well-maintained residential homes with manicured hedges border the streets, and utility lines run overhead\",\n",
    "    }\n",
    "    \n",
    "    scene_desc = base_scenes[scene_type]\n",
    "    \n",
    "    results = {\n",
    "        \"text\": [],\n",
    "        \"helping_positives\": [],\n",
    "        \"negatives\": [],\n",
    "        \"segformer_class_id\": data['segformer_class_id']\n",
    "    }\n",
    "    \n",
    "    for obj in objects:\n",
    "        print(f\"Processing object: {obj}\")\n",
    "        response = process_single_object(model, tokenizer, scene_desc, obj)\n",
    "        print(\"Response:\", response)\n",
    "        # print(\"-=-=-=-\")\n",
    "        parsed = parse_object_response(response)\n",
    "        \n",
    "        # Add to results\n",
    "        results[\"text\"].extend(parsed[\"text\"])\n",
    "        results[\"helping_positives\"].extend(parsed[\"helping_positives\"])\n",
    "        results[\"negatives\"].extend(parsed[\"negatives\"])\n",
    "        print(\"-----------------------------\")\n",
    "\n",
    "    with open(output_json, 'w') as f:\n",
    "        json.dump(results, f, indent=4)\n",
    "    \n",
    "    return results\n",
    "\n",
    "\n",
    "\n",
    "MODEL_SIZE = \"1B\" # Can be \"0.5B\", \"1.5B\", \"3B\", \"14B\", \"32B\", \"72B\"\n",
    "\n",
    "scene_names = [\"scene_012_30_forward\", \"scene_021_30_forward\", \"scene_036_30_forward\", \"scene_078_30_forward\", \"scene_088_50_forward\"]\n",
    "scene_types = [\"urban_construction\", \"urban_business_district\", \"residential_commercial\", \"urban_narrow_street\", \"suburban_residential\"]\n",
    "\n",
    "for scene_name, scene_type in zip([scene_names[0]], [scene_types[0]]):\n",
    "    input_json = f\"../configs/wayvescene-rendering/{scene_name}.json\"\n",
    "    output_json = f\"Llama/llama-{MODEL_SIZE}-Instruct_{scene_name}_indir.json\"\n",
    "    # os.makedirs(\"Qwen\", exist_ok=True)\n",
    "\n",
    "    results = process_scene_objects(\n",
    "        input_json=input_json,\n",
    "        output_json=output_json,\n",
    "        model_name=f\"unsloth/llama-3.2-{MODEL_SIZE}-Instruct\",\n",
    "        scene_type=scene_type\n",
    "    )\n",
    "    print(\"\\nProcessing complete. Results saved to:\", output_json)\n",
    "    print(\"\\nProcessed results:\", json.dumps(results, indent=2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "unsloth_env",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
